﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;

namespace Nirvana
{
    /// <summary>
    /// 中文翻译提取工具，用于提取Lua代码，Prefab，XML配置表中的中文
    /// </summary>
    [CreateAssetMenu(fileName = "@I18NChineseToCSV", menuName = "Nirvana/I18N/I18NChineseToCSV")]
    public sealed class I18NChineseToCSV : ScriptableObject
    {
        private enum languageType
        {
            zh_CN,
            en,
        }
        /////////////////////////////////CSV配置
        [Header("CSV配置")]
        [SerializeField]
        private languageType language = languageType.zh_CN;

        /// <summary>
        /// 保存路径
        /// </summary>
        [SerializeField]
        private string csvOutPath = "Assets/I18N/translate_origin.csv";

        //////////////////////////////////////////////脚本中文
        [Tooltip("脚本配置")]
        [SerializeField]
        private string scriptPath = "Assets/SkyChess/Lua";

        /// <summary>
        /// 言宽断言:匹配以： " 字符开头，不包括 "字符的任意字符和中文，以 "字符结尾，不包括"，
        /// </summary>
        [SerializeField]
        private string scriptMatchRegex = "(?<=\")([^\"]*)([\u4e00-\u9fa5]+)([^\"]*)(?=\")";
        //private string scriptRegex = "(?<=\")[^\"]*[\u4e00-\u9fa5]+[^\"]*(?=\")|(?<=\')[^\']*[\u4e00-\u9fa5]+[^\']*(?=\')";

        [Tooltip("忽略目录和文件.")]
        [SerializeField]
        private ListStringVariate ingoreFolderAndFile;

        [Tooltip("忽略日志等")]
        [SerializeField]
        private ListStringVariate ingoreLogAndOther;



        //////////////////////////////////Prefab中文
        [Tooltip("UI配置")]
        [SerializeField]
        private string uiPrefabPath = "Assets/Resources/UI";

        /// <summary>
        /// 中文Unicode编码
        /// </summary>
        [SerializeField]
        private string uiMatchRegex = "[\u4e00-\u9fa5]";

        [Tooltip("忽略哪些资源.")]
        [SerializeField]
        private ListStringVariate ignoreUIFolder;


        //////////////////////////////////xml配置表中文
        [Tooltip("Xml表配置")]
        [SerializeField]
        private string xmlPath = "E:/work/asset/airplane/xml/configs";

        /// <summary>
        /// 言宽断言:匹配以： " 字符开头，不包括 "字符的任意字符和中文，以 "字符结尾，不包括"，
        /// 用于在XML格式不规范，无法加载时，以字节流方式读取XML文件,因此需要判断”“符号开头。
        /// 此时需要检测注释<!-- -->
        /// </summary>
        [SerializeField]
        [Tooltip("用于在XML格式不规范，无法加载时，以字节流方式读取XML文件,因此需要判断”“符号开头。")]
        private string xmlRegex = "(?<=\")([^\"]*)([\u4e00-\u9fa5]+)([^\"]*)(?=\")";

        [Header("是否标注文本字符来源")]
        [SerializeField]
        private bool isOpenLog = false;

        [Tooltip("忽略哪些资源.")]
        [SerializeField]
        private ListStringVariate ingoreXmlFile;
        //////////////////////////////////CSV转PO翻译文件
        [Tooltip("CSV文件")]
        [SerializeField]
        private TextAsset csvFile;

        /// <summary>
        /// 保存路径
        /// </summary>
        [SerializeField]
        private string poOutPath = "Assets/I18N/translate_origin_pot.pot";

        [SerializeField]
        public string BugsAddress = "YOUR_EMail";
        [SerializeField]
        public string CopyrightHolder = "xhy";
        [SerializeField]
        public string CopyrightYear = "2022";
        [SerializeField]
        public string PackageName = "CoinPlan";
        [SerializeField]
        public string PackageVersion = "1.0.0";


        public void ScriptToChinese()
        {
            // 解析脚本
            List<string> keys = new List<string>();
            this.GenerateScriptKeys(ref keys);
            if (keys.Count > 0)
            {
                GenerateOriginFile(ref keys);
            }
        }

        public void UIPrefabToChinese()
        {
            // 解析脚本
            List<string> keys = new List<string>();
            this.GenerateUIKeys(ref keys);
            if (keys.Count > 0)
            {
                GenerateOriginFile(ref keys);
            }
        }

        public void XmlToChinese()
        {
            // 解析脚本
            List<string> keys = new List<string>();
            this.GenerateXmlKeys(ref keys);
            if (keys.Count > 0)
            {
                GenerateOriginFile(ref keys);
            }
        }

        public void CsvToPo()
        {
            // 解析脚本
            WriteCSVToPO(this.poOutPath);
        }

        private void GenerateOriginFile(ref List<string> keys)
        {
            Debug.Log(string.Format("[{0}]start generate origin config file...", System.DateTime.Now.ToShortTimeString()));
            var originFilePath = this.csvOutPath;
            var dir = Path.GetDirectoryName(originFilePath);
            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }
            // 检测原始文件是否已经存在，记录起来做增量判断
            Dictionary<string, string> oldDict = null;
            if (File.Exists(originFilePath))
            {
                //var ecode = GetEncoding(originFilePath); //电脑文件加密了，无法正常获取编码
                //if (ecode != Encoding.UTF8)
                //{
                //    Debug.LogError("Please Make Sure This  File Encoding With UTF8: " + Path.GetFileName(originFilePath));
                //    return;
                //}

                oldDict = Config2Dictionary(originFilePath);
            }

            using (ProgressIndicator progress = new ProgressIndicator("genreate csv", 2))
            {
                // 筛选出无用key和增量k
                if (oldDict != null)
                {
                    progress.Show("remove old keys and add new keys");
                    for (int i = keys.Count - 1; i >= 0; i--)
                    {
                        if (oldDict.ContainsKey(keys[i]))
                            keys.RemoveAt(i);
                    }
                    progress.AddProgress();
                }

                if (keys.Count > 0)
                {
                    progress.Show("generate origin file");

                    Dictionary<string, string> dictFinal = new Dictionary<string, string>();
                    foreach (var key in keys)
                    {
                        dictFinal[key] = "";
                    }

                    this.WriteConfigTranslate(originFilePath, dictFinal, "translate");
                    progress.AddProgress();
                }
                else
                {
                    Debug.Log("nothing to add");
                }
            }

            AssetDatabase.Refresh(ImportAssetOptions.Default);
            Debug.Log(string.Format("[{0}]finished", DateTime.Now.ToShortTimeString()));
        }

        private Encoding GetEncoding(string filename)
        {
            // Read the BOM
            var bom = new byte[4];
            using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read))
            {
                file.Read(bom, 0, 4);
            }
            // Analyze the BOM
            if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) return Encoding.UTF7;
            if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8;
            if (bom[0] == 0xff && bom[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
            if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
            if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) return Encoding.UTF32;
            return Encoding.ASCII;
        }

        private Dictionary<string, string> Config2Dictionary(string path, string sheetName = "translate")
        {
            Debug.Log(string.Format("read {0} start, isExist {1} ", path, File.Exists(path)));
            Dictionary<string, string> ret = new Dictionary<string, string>();

            if (!File.Exists(path)) return ret;

            string[] content = File.ReadAllLines(path);
            // 从第二行开始读取
            for (int i = 1; i < content.Length; i++)
            {
                string[] data = content[i].Split(',');
                if (data.Length < 2)
                {
                    // ...提取的中文数据有问题
                    Debug.LogError("提取的中文数据有问题:" + data[0]);
                    continue;
                }
                if (!ret.ContainsKey(data[0])) ret.Add(data[0], data[1]);
            }

            Debug.Log("read done");

            return ret;
        }

        bool GenerateScriptKeys(ref List<string> keys)
        {
            var rootPath = this.scriptPath;
            if (rootPath == "")
                return true;

            var strRegex = this.scriptMatchRegex;
            var files = Directory.GetFiles(rootPath, "*.lua", SearchOption.AllDirectories);

            using (ProgressIndicator progress = new ProgressIndicator("generate script keys", files.Length))
            {
                bool isIngoreFile = this.ingoreFolderAndFile.IsHasValue();
                var ingoreFiles = this.ingoreFolderAndFile.GetValue();

                bool isIngoreLog = this.ingoreLogAndOther.IsHasValue();
                var ingoreLogs = this.ingoreLogAndOther.GetValue();
                bool isIngore = false;
                foreach (var file in files)
                {
                    isIngore = false;
                    //Debug.Log("file: " + file);

                    //跳过忽略目录和文件
                    if (isIngoreFile)
                    {
                        foreach (var exclude in ingoreFiles)
                        {
                            if (!string.IsNullOrEmpty(exclude) && file.Contains(exclude))
                            {
                                isIngore = true;
                                break;
                            }
                        }

                        if (isIngore) continue;
                    }


                    if (progress.Show("generating script:{0}", file)) return false;

                    using (StreamReader sr = new StreamReader(file, Encoding.UTF8))
                    {
                        string line = sr.ReadLine();
                        while (line != null)
                        {
                            isIngore = false;
                            //跳过Log
                            if (isIngoreLog)
                            {
                                foreach (var exclude in ingoreLogs)
                                {
                                    if (!string.IsNullOrEmpty(exclude) && line.Contains(exclude))
                                    {
                                        isIngore = true;
                                        break;
                                    }
                                }
                                if (isIngore)
                                {
                                    line = sr.ReadLine();
                                    continue;
                                }
                            }
                            var isNotes = line.TrimStart().StartsWith("--"); //开头带注释--， 则注释的中文不检测
                            if (isNotes)
                            {
                                line = sr.ReadLine();
                                continue;
                            }

                            var matches = Regex.Matches(line, strRegex);
                            for (int i = 0; i < matches.Count; i++)
                            {
                                var match = matches[i];

                                string fixMatch = match.Value;
                                //Debug.Log(fixMatch);
                                fixMatch = fixMatch.Replace(',', '，'); //csv是以,分隔的
                                fixMatch = fixMatch.Replace("\n", "\\n");
                                fixMatch = fixMatch.Replace("\r", "\\r");

                                if (fixMatch != "" && !keys.Contains(fixMatch))
                                    keys.Add(fixMatch);
                            }

                            line = sr.ReadLine();
                        }
                    }

                    progress.AddProgress();
                }
            }

            return true;
        }

        bool GenerateUIKeys(ref List<string> keys)
        {
            var rootPath = this.uiPrefabPath;
            if (rootPath == "" | rootPath.StartsWith("#"))
                return true;


            bool isIgnoreUIFolder = this.ignoreUIFolder.IsHasValue();
            var ignoreUIFolders = this.ignoreUIFolder.GetValue();
            bool isIngore = false;

            var strRegex = this.uiMatchRegex;
            var guids = AssetDatabase.FindAssets("t:prefab", new string[] { rootPath });
            using (ProgressIndicator progress = new ProgressIndicator("generate ui keys", guids.Length))
            {
                foreach (var guid in guids)
                {
                    var path = AssetDatabase.GUIDToAssetPath(guid);
                    if (progress.Show("generating ui:{0}", path))
                        return false;

                    isIngore = false;
                    //跳过忽略目录和文件
                    if (isIgnoreUIFolder)
                    {
                        foreach (var exclude in ignoreUIFolders)
                        {
                            if (!string.IsNullOrEmpty(exclude) && path.Contains(exclude))
                            {
                                isIngore = true;
                                break;
                            }
                        }

                        if (isIngore) continue;
                    }

                    var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
                    var texts = prefab.GetComponentsInChildren<Text>(true);

                    foreach (var text in texts)
                    {

                        string content = text.text;
                        if (content != "" && Regex.IsMatch(content, strRegex))
                        {
                            if (content.EndsWith("\r"))
                            {
                                Debug.LogError(" [Warn:] Prefab has \\r char ,Start Auto clear: " + Path.GetFileName(path) + "/" + text.gameObject.name + "==" + content);
                                content = content.TrimEnd('\r'); //清除Prefab末尾的回车,PO文件不能以\\r换行，而是以\\n换行
                                text.text = content;
                                EditorUtility.SetDirty(text);
                            }
                            if (content.Contains("\r\n"))
                            {
                                Debug.LogError(" [Warn:] Prefab has \\r\\n char ,Start Auto clear: " + Path.GetFileName(path) + "/" + text.gameObject.name + "==" + content);
                                content = content.Replace("\r\n", "\n"); //清除Prefab文本的回车换行,PO文件不能以\\r换行,而是以\\n换行
                                text.text = content;
                                EditorUtility.SetDirty(text);
                            }


                            content = content.Replace("\n", "\\n");
                            //content = content.Replace("\r", "\\r");
                            content = content.Replace(',', '，');
                            content = content.Replace('"', '“'); //csv表格会以"作公式，所以要避免使用

                            if (!keys.Contains(content)) keys.Add(content);
                        }
                    }

                    progress.AddProgress();
                }
            }
            AssetDatabase.SaveAssets();
            return true;
        }

        bool GenerateXmlKeys(ref List<string> keys)
        {
            var rootPath = this.xmlPath;
            rootPath = rootPath.Replace("\\", "/");

            if (rootPath == "" | rootPath.StartsWith("#"))
                return true;

            var strRegex = this.xmlRegex;
            bool isIgnoreFile = this.ingoreXmlFile.IsHasValue();
            var ignoreFiles = this.ingoreXmlFile.GetValue();
            bool isIngore = false;

            string[] filesPath = Directory.GetFiles(rootPath, "*.xml", SearchOption.AllDirectories);
            using (ProgressIndicator progress = new ProgressIndicator("generate Xml keys", filesPath.Length))
            {
                foreach (var file in filesPath)
                {
                    if (progress.Show("generating ui:{0}", file))
                        return false;

                    isIngore = false;
                    //跳过忽略目录和文件
                    if (isIgnoreFile)
                    {
                        foreach (var exclude in ignoreFiles)
                        {
                            if (!string.IsNullOrEmpty(exclude) && file.Contains(exclude))
                            {
                                isIngore = true;
                                break;
                            }
                        }

                        if (isIngore) continue;
                    }
                    if (isOpenLog)
                    {
                        Debug.Log("-->" + file);
                        keys.Add("--------->注意：看到这行不要翻译！！！此处用于分隔文件标记的！ [[ " + Path.GetFileName(file) + " ]] <--------");
                    }

                    using (StreamReader sr = new StreamReader(file, Encoding.UTF8))
                    {
                        string line = sr.ReadLine();
                        while (line != null)
                        {
                            //检测注释<!-- -->
                            if (line.Contains("<!--"))
                            {
                                if (line.Contains("-->")) //单行注释
                                {
                                    line = sr.ReadLine(); //读新行
                                    continue;
                                }
                                else //多行注释
                                {
                                    do
                                    {
                                        line = sr.ReadLine();
                                    } while (!line.Contains("-->"));
                                }

                            }

                            var matchesCDATA = Regex.Matches(line, "(<![CDATA[)(.*)([\u4e00-\u9fa5]+)(.*)(]]>)");//纯文本表达式
                            if (matchesCDATA.Count > 0) Debug.Log("See : " + Path.GetFileName(file) + " , have <![CDATA[ format");
                            for (int i = 0; i < matchesCDATA.Count; i++)
                            {
                                var match = matchesCDATA[i];
                                string fixMatch = match.Value;
                                line = line.Replace(fixMatch, "");
                                fixMatch = fixMatch.Replace("<![CDATA[", "").Replace("]]>", "");
                                //Debug.Log(fixMatch);
                                //Debug.Log(line);

                                if (fixMatch != "" && !keys.Contains(fixMatch))
                                    keys.Add(fixMatch);
                            }


                            var matches = Regex.Matches(line, strRegex);
                            for (int i = 0; i < matches.Count; i++)
                            {
                                var match = matches[i];

                                string fixMatch = match.Value;
                                //Debug.Log(fixMatch);
                                fixMatch = fixMatch.Replace(',', '，'); //csv是以,分隔的
                                fixMatch = fixMatch.Replace("&amp;", "&")
                                    .Replace("&lt;", "<")
                                    .Replace("&gt;", ">")
                                    .Replace("&apos;", "'")
                                    .Replace("&quot;", "\"")
                                    .Replace("&#x000A;", "\\n");

                                //xml的一些符号编码
                                var regex16 = new Regex("&#x([0-9a-fA-F]+);"); //16进制
                                if (regex16.IsMatch(fixMatch))
                                {
                                    var ms = regex16.Matches(fixMatch);
                                    for (int j = 0; j < ms.Count; j++)
                                    {
                                        string _sign = ms[j].Value;
                                        var r2 = Regex.Match(_sign, "([0-9a-fA-F]+)");

                                        var h = System.Convert.ToInt32(r2.Value, 16);
                                        var asc = Chr(h);
                                        fixMatch = fixMatch.Replace(_sign, asc);
                                    }
                                }

                                var regex10 = new Regex("&#([0-9]+);"); //10进制
                                if (regex10.IsMatch(fixMatch))
                                {
                                    var ms = regex10.Matches(fixMatch);
                                    for (int j = 0; j < ms.Count; j++)
                                    {
                                        string _sign = ms[j].Value;
                                        var r2 = Regex.Match(_sign, "([0-9]+)");

                                        var h = System.Convert.ToInt32(r2.Value, 10);
                                        var asc = Chr(h);
                                        fixMatch = fixMatch.Replace(_sign, asc);
                                    }
                                }

                                if (fixMatch != "" && !keys.Contains(fixMatch))
                                    keys.Add(fixMatch);
                            }
                            line = sr.ReadLine();
                        }
                    }


                    progress.AddProgress();
                }
            }

            return true;

        }

        /// <summary>
        /// 符号转ASCII码
        /// </summary>
        /// <param name="character"></param>
        /// <returns></returns>
        public int Asc(string character)
        {
            if (character.Length == 1)
            {
                System.Text.ASCIIEncoding asciiEncoding = new System.Text.ASCIIEncoding();
                int intAsciiCode = (int)asciiEncoding.GetBytes(character)[0];
                return (intAsciiCode);
            }
            else
            {
                throw new Exception("Character is not valid.");
            }
        }

        /// <summary>
        /// Ascii码转符号
        /// </summary>
        /// <param name="asciiCode"></param>
        /// <returns></returns>
        public string Chr(int asciiCode)
        {
            if (asciiCode >= 0 && asciiCode <= 255)
            {
                System.Text.ASCIIEncoding asciiEncoding = new System.Text.ASCIIEncoding();
                byte[] byteArray = new byte[] { (byte)asciiCode };
                string strCharacter = asciiEncoding.GetString(byteArray);
                return (strCharacter);
            }
            else
            {
                throw new Exception("ASCII Code is not valid.");
            }
        }

        void WriteConfigTranslate(string path, Dictionary<string, string> dictionary, string sheetName = "translate")
        {
            Debug.Log(string.Format("write {0} start", path));

            if (File.Exists(path))
            {
                if (dictionary.Count > 0)
                {
                    StringBuilder sb = new StringBuilder();
                    foreach (KeyValuePair<string, string> item in dictionary)
                    {
                        sb.AppendLine(item.Key + "," + item.Value);
                    }

                    File.AppendAllText(path, sb.ToString(), Encoding.UTF8);
                }
                Debug.Log("Append write done");
            }
            else
            {
                if (dictionary.Count > 0)
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine("Key,Value");
                    foreach (KeyValuePair<string, string> item in dictionary)
                    {
                        sb.AppendLine(item.Key + "," + item.Value);
                    }
                    File.WriteAllText(path, sb.ToString(), Encoding.UTF8);
                }
                Debug.Log(" Create write done");
            }
        }

        void WriteCSVToPO(string path)
        {
            if (csvFile != null)
            {

                Dictionary<string, string> dictionary = new Dictionary<string, string>();
                MemoryStream ms = new MemoryStream(csvFile.bytes);
                using (StreamReader sr = new StreamReader(ms, Encoding.UTF8))
                {
                    while (sr.Peek() >= 0)
                    {
                        string line = sr.ReadLine();
                        if (string.IsNullOrEmpty(line)) continue;
                        Debug.Log(line);

                        string[] data = line.Split(',');
                        if (data.Length < 2)
                        {
                            // ...提取的中文数据有问题
                            Debug.LogError("提取的中文数据有问题:" + data[0]);
                            continue;
                        }
                        if (!dictionary.ContainsKey(data[0])) dictionary.Add(data[0], data[1]);
                    }
                }
                ms.Dispose();

                if (dictionary.Count > 0)
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine(GetPoHead());
                    foreach (KeyValuePair<string, string> item in dictionary)
                    {
                        sb.AppendLine();
                        sb.Append(GetPoBody(item.Key, item.Value));
                    }

                    File.AppendAllText(path, sb.ToString(), Encoding.UTF8);
                }

                AssetDatabase.Refresh();
            }
        }

        private string GetPoHead()
        {
            var time = DateTime.Now.ToString("yyyy-MM-dd HH:m:sszzz").Remove(22, 1);
            StringBuilder sb = new StringBuilder();
            sb.AppendLine(string.Format("# Copyright (C) {0} {1} ", CopyrightYear, CopyrightHolder));
            sb.AppendLine(string.Format("# This file is distributed under the same license as the {0} package", PackageName));
            sb.AppendLine(string.Format("# FIRST AUTHOR <EMAIL@ADDRESS>, {0}", CopyrightYear));
            sb.AppendLine(@"msgid """"");
            sb.AppendLine(@"msgstr """"");
            sb.AppendLine(string.Format(@"""Project-Id-Version: {0} {1}\n""", PackageName, PackageVersion));
            sb.AppendLine(string.Format(@"""Report-Msgid-Bugs-To: {0}\n""", BugsAddress));
            sb.AppendLine(string.Format(@"""POT-Creation-Date: {0}\n""", time));
            sb.AppendLine(string.Format(@"""PO-Revision-Date: {0}\n""", time));
            sb.AppendLine(@"""Last-Translator: FULL NAME <EMAIL@ADDRESS>\n""");
            sb.AppendLine(@"""Language-Team: LANGUAGE <LL@li.org>\n""");
            sb.AppendLine(string.Format(@"""Language: {0}\n""", language.ToString().ToLower()));
            sb.AppendLine(@"""MIME-Version: 1.0\n""");
            sb.AppendLine(@"""Content-Type: text/plain; charset=UTF-8\n""");
            sb.AppendLine(@"""Content-Transfer-Encoding: 8bit\n""");
            sb.AppendLine(@"""Plural-Forms: nplurals=1; plural=0;\n""");
            return sb.ToString();
        }

        private string GetPoBody(string msgid, string msgstr)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine(string.Format("msgid \"{0}\"", msgid));
            sb.AppendLine(string.Format("msgstr \"{0}\"", msgstr));
            return sb.ToString();
        }
    }
}

